/**
 * BeanUtils
 *
 * @author Chris Pratt
 *
 * 10/23/2007
 */
package com.anodyzed.onyx.bean;

import com.anodyzed.onyx.log.Log;
import com.anodyzed.onyx.log.LogBuilder;
import com.anodyzed.onyx.text.TextFormat;
import com.anodyzed.onyx.type.Converter;
import com.anodyzed.onyx.type.ConverterFactory;
import com.anodyzed.onyx.util.Convert;
import com.anodyzed.onyx.util.Misc;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class BeanUtils {
  private static final Log log = LogBuilder.getLogger();

  /**
   * Private Constructor
   */
  private BeanUtils () {
  } //BeanUtils

  /**
   * Get the Property Descriptor of the property in the supplied class
   *
   * @param clazz The Class to Introspect 
   * @param property The Name of the Property to locate 
   * @return The Property Descriptor
   * @throws IntrospectionException if we get lost in introspection
   */
  public static PropertyDescriptor getDescriptor (Class<?> clazz,String property) throws IntrospectionException {
    for(PropertyDescriptor descriptor : Introspector.getBeanInfo(clazz).getPropertyDescriptors()) {
      if(property.equals(descriptor.getName())) {
        return descriptor;
      }
    }
    return null;
  } //getDescriptor

  /**
   * Get the Property Accessor for the specified property name
   *
   * @param clazz The Bean Class 
   * @param property The Property Name 
   * @return The Property Accessor Method
   * @throws IntrospectionException if we get lost in introspection
   */
  public static Method getAccessor (Class<?> clazz,String property) throws IntrospectionException {
    PropertyDescriptor descriptor = getDescriptor(clazz,property);
    return (descriptor != null) ? descriptor.getReadMethod() : null;
  } //getAccessor

  /**
   * Get the List of Accessors
   *
   * @param clazz The Bean Class
   * @return The List of Accessor Methods
   * @throws IntrospectionException if we get lost in introspection
   */
  public static List<Method> getAccessors (Class<?> clazz) throws IntrospectionException {
    Method accessor;
    List<Method> accessors = new ArrayList<>();
    for(PropertyDescriptor descriptor : Introspector.getBeanInfo(clazz).getPropertyDescriptors()) {
      if((accessor = descriptor.getReadMethod()) != null) {
        accessors.add(accessor);
      }
    }
    return accessors;
  } //getAccessors

  /**
   * Get the List of Properties
   *
   * @param clazz The Bean Class
   * @return The Set of Property Names
   * @throws IntrospectionException if we get lost in introspection
   */
  public static Set<String> getProperties (Class<?> clazz) throws IntrospectionException {
    Set<String> properties = new HashSet<>();
    for(PropertyDescriptor descriptor : Introspector.getBeanInfo(clazz).getPropertyDescriptors()) {
      if(descriptor.getReadMethod() != null) {
        properties.add(descriptor.getName());
      }
    }
    return properties;
  } //getProperties

  /**
   * Get the Property Mutator for the specified property name
   *
   * @param clazz The Bean Class 
   * @param property The Property Name 
   * @return The Property Mutator Method
   * @throws IntrospectionException if we get lost in introspection
   */
  public static Method getMutator (Class<?> clazz,String property) throws IntrospectionException {
    PropertyDescriptor descriptor = getDescriptor(clazz,property);
    return (descriptor != null) ? descriptor.getWriteMethod() : null;
  } //getMutator

  /**
   * Get the Type of the Specified Property in the supplied Class
   *
   * @param clazz The Bean Class 
   * @param property The Property Name 
   * @return The Property Type Class
   * @throws IntrospectionException if we get lost in introspection
   */
  public static Class<?> getPropertyType (Class<?> clazz,String property) throws IntrospectionException {
    PropertyDescriptor descriptor = getDescriptor(clazz,property);
    return (descriptor != null) ? descriptor.getPropertyType() : null;
  } //getPropertyType

  /**
   * Get the Class for elements in the named Collection or Array
   *
   * @param parent The Bean Context
   * @param property The (possibly dot notated) bean property
   * @return The element type
   */
  public static Class<?> getElementType (Object parent,String property) throws IntrospectionException {
    int dot = property.lastIndexOf('.');
    Object bean = (dot != -1) ? getValue(parent,property.substring(0,dot)) : parent;
    String prop = (dot != -1) ? property.substring(dot + 1) : property;
    Class<?> cls = bean.getClass();
    try {
      Field field = cls.getDeclaredField(prop);
      if(field.isAnnotationPresent(RTTI.class)) {
        return field.getAnnotation(RTTI.class).value();
      } else {
        log.trace("No RTTI Annotation Present on {0.declaringClass.name}.{0.name}",field);
      }
    } catch(NoSuchFieldException x) {
      log.trace("Can''t locate the {0} field of {1}",property,parent,x);
    }
    Method accessor = getAccessor(cls,prop);
    if(accessor != null) {
      Type type = accessor.getGenericReturnType();
      if(type instanceof ParameterizedType) {
        Type[] args = ((ParameterizedType)type).getActualTypeArguments();
        if((args != null) && (args.length > 0) && (args[0] instanceof Class<?>)) {
          return (Class<?>)args[0];
        }
      }
    } else {
      log.trace("No Accessor for {0} in {1.name}",prop,cls);
    }
    if((bean instanceof Collection<?>) && !((Collection<?>)bean).isEmpty()) {
      Object testval = ((Collection<?>)bean).iterator().next();
      if(testval != null) {
        return testval.getClass();
      }
    }
    return null;
  } //getElementType

  /**
   * Turn the Method Name into the Property it manages by removing "get", "set", 
   * or "is" and lowercasing the new first character. 
   *
   * @param method The Property management method
   * @return The property name
   */
  public static String toPropertyName (Method method) {
    String name = method.getName();
    if((name.startsWith("get") || name.startsWith("set")) && (name.length() > 3)) {
      return Character.toLowerCase(name.charAt(3)) + name.substring(4);
    } else if(name.startsWith("is") && (name.length() > 2)) {
      return Character.toLowerCase(name.charAt(2)) + name.substring(3);
    }
    return name;
  } //toPropertyName

  /**
   * Determine whether the supplied class contains a, possibly dot-notated, 
   * property with the specified name. 
   *
   * @param clazz The Bean Class 
   * @param property the dot-notated property name 
   * @return true if available
   * @throws IntrospectionException if we get lost in introspection
   */
  public static boolean containsProperty (Class<?> clazz,String property) throws IntrospectionException {
    if(property != null) {
      int pos;
      String parent = property,child = null;
      if((pos = property.indexOf('.')) != -1) {
        child = property.substring(pos + 1);
        parent = property.substring(0,pos);
      }
      if(getProperties(clazz).contains(parent)) {
        return (child == null) || containsProperty(getAccessor(clazz,parent).getReturnType(),child);
      }
    }
    return false;
  } //containsProperty

  /**
   * Check whether the bean contains the value of the (possibly dot notated) Property
   *
   * @param bean The Bean to access 
   * @param property The Property name
   * @param factory The property value conversion factory
   * @return The property value from the bean
   * @throws IntrospectionException if we get lost in introspection
   */
  public static boolean contains (Object bean,String property,ConverterFactory factory) throws IntrospectionException {
    if(bean != null) {
      Class<?> cls = bean.getClass();
      try {
        int dot = property.indexOf('.');
        if(dot == -1) {
          if(property.equals("size") || property.equals("length")) {
            if(bean instanceof Collection<?>) {
              return true;
            } else if(bean instanceof Map<?,?>) {
              return true;
            } else if(bean instanceof String) {
              return true;
            } else if(cls.isArray()) {
              return true;
            } else if(getAccessor(cls,property) != null) {
              return true;
            } else {
              try {
                return cls.getMethod(property) != null;
              } catch(NoSuchMethodException x) {
                // Just return false
              }
            }
          } else if(bean instanceof Map<?,?>) {
            return ((Map<?,?>)bean).containsKey(property);
          } else {
            return getAccessor(cls,property) != null;
          }
        } else {
          Object subbean = getBean(bean,property.substring(0,dot),factory);
          return (subbean != null) && contains(subbean,property.substring(dot + 1));
        }
      } catch(IllegalAccessException x) {
        Class<?>[] interfaces = cls.getInterfaces();
        for(Class<?> i : interfaces) {
          if(getAccessor(i,property) != null) {
            return true;
          }
        }
        IntrospectionException y = new IntrospectionException("Unable to locate " + property + " property of " + bean);
        y.initCause(x);
        throw y;
      } catch(InvocationTargetException x) {
        IntrospectionException y = new IntrospectionException("Unable to locate " + property + " property of " + bean);
        y.initCause(x);
        throw y;
      }
    }
    return false;
  } //contains

  /**
   * Check whether the bean contains the value of the (possibly dot notated) Property
   *
   * @param bean The Bean to access 
   * @param property The Property name 
   * @return The property value from the bean
   * @throws IntrospectionException if we get lost in introspection
   */
  public static boolean contains (Object bean,String property) throws IntrospectionException {
    return contains(bean,property,ConverterFactory.getDefaultFactory());
  } //contains

  /**
   * Get the Bean property of the supplied bean
   *
   * @param bean The parent bean 
   * @param property The bean child name 
   * @param factory The Type Converter Factory 
   * @return The child bean
   * @throws IntrospectionException if we get lost in introspection
   * @throws IllegalAccessException if we try to touch something we're not allowed to
   * @throws InvocationTargetException if something goes wrong within the constructor of the bean
   */
  private static Object getBean (Object bean,String property,ConverterFactory factory) throws IntrospectionException, IllegalAccessException, InvocationTargetException {
    int l;
    String index = null;
    String prop = property;
    if((l = property.indexOf('[')) != -1) {
      int r = property.indexOf(']',l);
      if(r != -1) {
        index = property.substring(l + 1,r);
      }
      prop = property.substring(0,l);
    }
    Object ret;
    Method accessor = null;
    Class<?> beantype = bean.getClass();
    if(bean instanceof Map<?,?>) {
      ret = ((Map<?,?>)bean).get(prop);
    } else {
      accessor = getAccessor(beantype,prop);
      ret = (accessor != null) ? accessor.invoke(bean) : null;
    }
    if((ret != null) && (index != null)) {
      if(ret instanceof Map<?,?>) {
        Class<?> keytype = null;
        try {
          Field field = beantype.getDeclaredField(prop);
          if(field.isAnnotationPresent(RTTI.class)) {
            keytype = field.getAnnotation(RTTI.class).key();
          } else {
            log.trace("No RTTI Annotation Present getting {0.declaringClass.name}.{0.name} map",field);
          }
        } catch(NoSuchFieldException x) {
          log.trace("Can''t locate the {0} field of {1}",prop,bean,x);
        }
        if(keytype == null) {
          if(accessor == null) {
            accessor = getAccessor(beantype,prop);
          }
          if(accessor != null) {
            Type type = accessor.getGenericReturnType();
            if(type instanceof ParameterizedType) {
              Type[] args = ((ParameterizedType)type).getActualTypeArguments();
              if((args != null) && (args.length > 0)) {
                if(args[0] instanceof Class<?>) {
                  keytype = (Class<?>)args[0];
                }
              }
            }
            if(keytype == null) {
              if(!((Map<?,?>)ret).isEmpty()) {
                Object testkey = ((Map<?,?>)ret).keySet().iterator().next();
                if(testkey != null) {
                  keytype = testkey.getClass();
                }
              }
            }
          }
        }
        ret = ((Map<?,?>)ret).get(convertArg(index,keytype,factory));
      } else if(Misc.isNumeric(index)) {
        int ndx = Convert.toInt(index,0);
        if(ret instanceof List<?>) {
          ret = (((List<?>)ret).size() > ndx) ? ((List<?>)ret).get(ndx) : null;
        } else if(ret.getClass().isArray()) {
          ret = (Array.getLength(ret) > ndx) ? Array.get(ret,ndx) : null;
        }
      }
    }
    return ret;
  } //getBean

  /**
   * Get the value of the (possibly dot notated) Property from the Bean
   *
   * @param bean The Bean to access 
   * @param property The Property name 
   * @param factory The Type Converter Factory 
   * @return The property value from the bean
   * @throws IntrospectionException if we get lost in introspection
   */
  public static Object getValue (Object bean,String property,ConverterFactory factory) throws IntrospectionException {
    property = property.trim();
    char ch = property.charAt(0);
    if(((ch == '"') || (ch == '\'')) && (property.charAt(property.length() - 1) == ch)) {
      return property.substring(1,property.length() - 1);
    } else if(bean != null) {
      Method accessor;
      Class<?> cls = bean.getClass();
      try {
        int dot = property.indexOf('.');
        if(dot == -1) {
          if(property.equals("size") || property.equals("length")) {
            if(bean instanceof Collection<?>) {
              return ((Collection<?>)bean).size();
            } else if(bean instanceof Map<?,?>) {
              return ((Map<?,?>)bean).size();
            } else if(bean instanceof String) {
              return ((String)bean).length();
            } else if(cls.isArray()) {
              return Array.getLength(bean);
            } else if((accessor = getAccessor(cls,property)) != null) {
              return accessor.invoke(bean);
            } else {
              try {
                return cls.getMethod(property).invoke(bean);
              } catch(NoSuchMethodException x) {
                // Just return null
              }
            }
          } else {
            return getBean(bean,property,factory);
          }
        } else {
          Object subbean = getBean(bean,property.substring(0,dot),factory);
          return (subbean != null) ? getValue(subbean,property.substring(dot + 1)) : null;
        }
      } catch(IllegalAccessException x) {
        Class<?>[] interfaces = cls.getInterfaces();
        for(Class<?> i : interfaces) {
          try {
            if((accessor = getAccessor(i,property)) != null) {
              return accessor.invoke(bean);
            }
          } catch(IllegalAccessException y) {
              // Nop
          } catch(InvocationTargetException y) {
            IntrospectionException z = new IntrospectionException("Unable to introspect " + property + " property of " + bean);
            z.initCause(y);
            throw z;
          }
        }
        IntrospectionException y = new IntrospectionException("Unable to introspect " + property + " property of " + bean);
        y.initCause(x);
        throw y;
      } catch(InvocationTargetException x) {
        IntrospectionException y = new IntrospectionException("Unable to introspect " + property + " property of " + bean);
        y.initCause(x);
        throw y;
      }
    }
    return null;
  } //getValue

  /**
   * Get the value of the (possibly dot notated) Property from the Bean
   *
   * @param bean The Bean to access 
   * @param property The Property name 
   * @return The property value from the bean
   * @throws IntrospectionException if we get lost in introspection
   */
  public static Object getValue (Object bean,String property) throws IntrospectionException {
    return getValue(bean,property,ConverterFactory.getDefaultFactory());
  } //getValue

  /**
   * Get the string value of the (possibly dot notated) Property from the 
   * supplied bean 
   *
   * @param bean The Bean to access 
   * @param property The property name 
   * @return The String property value
   */
  public static String getString (Object bean,String property) {
    try {
      return String.valueOf(getValue(bean,property));
    } catch(IntrospectionException x) {
      log.warn("Error Introspecting {1} property of {0}",bean,property,x);
      return "null";
    }
  } //getString

  /**
   * Test if the supplied primitive Type matches the supplied wrapper type
   * (i.e. int and Integer)
   *
   * @param primitive the Class with isPrimitive == true 
   * @param wrapper the Class is isPrimitive == false 
   * @return true if equivalent
   */
  private static boolean primitiveEquivalence (Class<?> primitive,Class<?> wrapper) {
    if(primitive == Boolean.TYPE) {
      return wrapper.equals(Boolean.class);
    } else if(primitive == Byte.TYPE) {
      return wrapper.equals(Byte.class);
    } else if(primitive == Character.TYPE) {
      return wrapper.equals(Character.class);
    } else if(primitive == Double.TYPE) {
      return wrapper.equals(Double.class);
    } else if(primitive == Float.TYPE) {
      return wrapper.equals(Float.class);
    } else if(primitive == Integer.TYPE) {
      return wrapper.equals(Integer.class);
    } else if(primitive == Long.TYPE) {
      return wrapper.equals(Long.class);
    } else if(primitive == Short.TYPE) {
      return wrapper.equals(Short.class);
    }
    return false;
  } //primitiveEquivalence

  /**
   * Test the supplied types for equivalence including equivalence between 
   * primitives and their corresponding wrapper classes.
   *
   * @param type1 The first type 
   * @param type2 The other type
   * @return true if equivalent
   */
  public static boolean equivalent (Class<?> type1,Class<?> type2) {
    if(!type1.isAssignableFrom(type2)) {
      if(type1.isPrimitive()) {
        return primitiveEquivalence(type1,type2);
      } else if(type2.isPrimitive()) {
        return primitiveEquivalence(type2,type1);
      }
      return false;
    }
    return true;
  } //equivalent

  /**
   * Test whether the supplied type is numeric
   *
   * @param type The questionable type
   * @return true if numeric
   */
  public static boolean isNumeric (Class<?> type) {
    return Number.class.isAssignableFrom(type) || (type.isPrimitive() && (type == Long.TYPE) || (type == Integer.TYPE) || (type == Short.TYPE) || (type == Byte.TYPE) || (type == Double.TYPE) || (type == Float.TYPE));
  } //isNumeric

  /**
   * Test whether a number of the first type can be assigned to a number of
   * the second type (i.e. an Int can always be assigned to a long).
   *
   * NB: Same type match (e.g. int -> int or even long -> Long) would have
   * been caught by {@link #equivalent}
   *
   * @param type1 The Left Type
   * @param type2 The Right Type
   * @return true if numerically assignable
   */
  private static boolean numericallyAssignable (Class<?> type1,Class<?> type2) {
    if(isNumeric(type1) && isNumeric(type2)) {
      if((type2 == Long.TYPE) || (type2 == Long.class)) {
        return (type1 == Integer.TYPE) || (type1 == Integer.class) || (type1 == Short.TYPE) || (type1 == Short.class) || (type1 == Byte.TYPE) || (type1 == Byte.class);
      } else if((type2 == Integer.TYPE) || (type2 == Integer.class)) {
        return (type1 == Short.TYPE) || (type1 == Short.class) || (type1 == Byte.TYPE) || (type1 == Byte.class);
      } else if((type2 == Short.TYPE) || (type2 == Short.class)) {
        return (type1 == Byte.TYPE) || (type1 == Byte.class);
      } else if((type2 == Double.TYPE) || (type2 == Double.class)) {
        return (type1 == Float.TYPE) || (type1 == Float.class) || (type1 == Long.TYPE) || (type1 == Integer.class) || (type1 == Integer.TYPE) || (type1 == Integer.class) || (type1 == Short.TYPE) || (type1 == Short.class) || (type1 == Byte.TYPE) || (type1 == Byte.class);
      } else if((type2 == Float.TYPE) || (type2 == Float.class)) {
        return (type1 == Long.TYPE) || (type1 == Integer.class) || (type1 == Integer.TYPE) || (type1 == Integer.class) || (type1 == Short.TYPE) || (type1 == Short.class) || (type1 == Byte.TYPE) || (type1 == Byte.class);
      }
    }
    return false;
  }  //numericallyAssignable

  /**
   * Convert the incoming Argument to the expected type, if possible
   *
   * @param arg The incoming Argument 
   * @param type The expected Type 
   * @param factory The ConverterFactory to use for Type Conversions 
   * @return The converted Argument
   */
  public static Object convertArg (Object arg,Class<?> type,ConverterFactory factory) {
    if(arg != null) {
      if(type != null) {
        Class<?> argType = arg.getClass();
        if(!equivalent(type,argType)) {
          if(!numericallyAssignable(argType,type)) {
            Converter converter = factory.getConverter(argType,type);
            if(converter != null) {
              return converter.convert(arg,type);
            } else {
              log.warn("Argument ({0.name}) is not convertible to {1.name}",argType,type);
            }
          } else {
            log.trace("No Conversion Necessary: {0.name} <=> {1.name}",argType,type);
          }
        } else {
          log.trace("No Conversion Necessary: {0.name} <-> {1.name}",argType,type);
        }
      } else {
        log.trace("Expected Type is null, not converting {0}",arg);
      }
    } else {
      log.trace("Argument is null");
    }
    return arg;
  } //convertArg

  /**
   * Set the property of the bean.  Dot notation is not taken into account, 
   * but indexed properties are handled properly.
   *
   * @param bean The Bean to access 
   * @param property The Property name 
   * @param value The new Property value
   * @param factory The property value type conversion factory
   * @return The previous property value from the bean
   * @throws IntrospectionException if we get lost in introspection
   * @throws IllegalAccessException if we try to touch something we're not allowed to
   * @throws InstantiationException if we can't create the resulting bean
   * @throws InvocationTargetException if something goes wrong within the constructor of the bean
   */
  @SuppressWarnings("unchecked")
  private static Object setBean (Object bean,String property,Object value,ConverterFactory factory) throws IllegalAccessException,InstantiationException,IntrospectionException,InvocationTargetException {
    int l;
    String index = null;
    String prop = property;
    if((l = property.indexOf('[')) != -1) {
      int r = property.indexOf(']',l);
      if(r != -1) {
        index = property.substring(l + 1,r);
      }
      prop = property.substring(0,l);
    }
    Class<?> beancls = bean.getClass();
    if(index == null) {
        // Not an indexed property, handle it directly
      if(bean instanceof Map<?,?>) {
        return ((Map<Object,Object>)bean).put(prop,value);
      } else {
        PropertyDescriptor desc = getDescriptor(beancls,prop);
        if(desc != null) {
          try {
            Method mutator = desc.getWriteMethod();
            return (mutator != null) ? mutator.invoke(bean,convertArg(value,desc.getPropertyType(),factory)) : null;
          } catch(IllegalArgumentException x) {
            IntrospectionException y = new IntrospectionException(TextFormat.format("Unable to Invoke {0.writeMethod.name}({0.propertyType}) with a {1.class.name} parameter",desc,value));
            y.initCause(x);
            throw y;
          }
        }
      }
    } else {
        // Indexed property
      Method accessor;
      Object subbean = null;
      if(bean instanceof Map<?,?>) {
        if((subbean = ((Map<?,?>)bean).get(prop)) == null) {
          if(!((Map<?,?>)bean).isEmpty()) {
            Object testval = ((Map<?,?>)bean).values().iterator().next();
            if(testval != null) {
              subbean = testval.getClass().newInstance();
              ((Map<String,Object>)bean).put(property,subbean);
            }
          }
        }
      } else {
        accessor = getAccessor(beancls,prop);
        if(accessor != null) {
          if((subbean = accessor.invoke(bean)) == null) {
            Method mutator = getMutator(beancls,prop);
            if(mutator != null) {
              subbean = accessor.getReturnType().newInstance();
              mutator.invoke(bean,subbean);
            } else {
              throw new IntrospectionException(TextFormat.format("Unable to initialize the {0} property of the class {1.name} ({2})",prop,beancls,property));
            }
          }
        }
      }
      if(subbean != null) {
        if(subbean instanceof Map<?,?>) {
          return ((Map<String,Object>)subbean).put(index,value);
        } else if(subbean instanceof List<?>) {
          if(Misc.isNumeric(index)) {
            int ndx = Integer.parseInt(index);
            if(((List<?>)subbean).size() > ndx) {
              return ((List<Object>)subbean).set(ndx,value);
            } else {
              ((List<Object>)subbean).add(value);
            }
          }
        }
      }
    }
    return null;
  } //setBean

  /**
   * Set the value of the (possibly dot notated) Property from the Bean
   *
   * @param bean The Bean to access 
   * @param property The Property name 
   * @param value The new Property value
   * @param factory The property value type conversion factory
   * @return The previous property value from the bean
   * @throws IntrospectionException if we get lost in introspection
   */
  @SuppressWarnings("unchecked")
  public static Object setValue (Object bean,String property,Object value,ConverterFactory factory) throws IntrospectionException {
    property = property.trim();
    char ch = property.charAt(0);
    if(((ch == '"') || (ch == '\'')) && (property.charAt(property.length() - 1) == ch)) {
      return property.substring(1,property.length() - 1);
    } else if(bean != null) {
      try {
        int dot = property.indexOf('.');
        if(dot == -1) {
            // No Dot, this is the final node we will be setting
          return setBean(bean,property,value,factory);
        } else {
            // Dot indicates just another link in the chain, keep recursing
          int l;
          String index = null;
          String prop = property.substring(0,dot);
          if((l = prop.indexOf('[')) != -1) {
            int r = prop.indexOf(']',l);
            if(r != -1) {
              index = prop.substring(l + 1,r);
            }
            prop = prop.substring(0,l);
          }
          Object subbean = null;
          Method accessor = null;
          Class<?> cls = bean.getClass();
          if(bean instanceof Map<?,?>) {
            if((subbean = ((Map<?,?>)bean).get(prop)) == null) {
              if(!((Map<?,?>)bean).isEmpty()) {
                Object testval = ((Map<?,?>)bean).values().iterator().next();
                if(testval != null) {
                  subbean = testval.getClass().newInstance();
                  ((Map<String,Object>)bean).put(prop,subbean);
                }
              }
            }
          } else {
            accessor = getAccessor(cls,prop);
            if(accessor != null) {
              if((subbean = accessor.invoke(bean)) == null) {
                Method mutator = getMutator(cls,prop);
                if(mutator != null) {
                  subbean = accessor.getReturnType().newInstance();
                  mutator.invoke(bean,subbean);
                } else {
                  throw new IntrospectionException(TextFormat.format("Unable to initialize the {0} property of the class {1.name} ({2})",prop,cls,property));
                }
              }
            }
          }
          if(subbean != null) {
            if(index != null) {
              int ndx = (Misc.isNumeric(index)) ? Integer.parseInt(index) : -1;
              if(subbean instanceof Map<?,?>) {
                Class<?> keytype = null;
                Class<?> valtype = null;
                try {
                  Field field = cls.getDeclaredField(prop);
                  if(field.isAnnotationPresent(RTTI.class)) {
                    RTTI rtti = field.getAnnotation(RTTI.class);
                    keytype = rtti.key();
                    valtype = rtti.value();
                  } else {
                    log.trace("No RTTI Annotation Present on {0.declaringClass.name}.{0.name} map",field);
                  }
                } catch(NoSuchFieldException x) {
                  log.trace("Can''t locate the {0} field of {1}",prop,bean,x);
                }
                if(keytype == null) {
                  if(accessor == null) {
                    accessor = getAccessor(cls,prop);
                  }
                  Type type = accessor.getGenericReturnType();
                  if(type instanceof ParameterizedType) {
                    Type[] args = ((ParameterizedType)type).getActualTypeArguments();
                    if((args != null) && (args.length > 1)) {
                      log.trace("Actual Type Arguments: <{0,array,csv}>",(Object)args);
                      if(args[0] instanceof Class<?>) {
                        keytype = (Class<?>)args[0];
                      }
                      if(args[1] instanceof Class<?>) {
                        valtype = (Class<?>)args[1];
                      }
                    }
                  }
                  if(keytype == null) {
                    if(!((Map<?,?>)subbean).isEmpty()) {
                      Object testkey = ((Map<?,?>)subbean).keySet().iterator().next();
                      if(testkey != null) {
                        keytype = testkey.getClass();
                      }
                      Object testval = ((Map<?,?>)subbean).values().iterator().next();
                      if(testval != null) {
                        valtype = testval.getClass();
                      }
                    }
                  }
                }
                Object key = convertArg(index,keytype,factory);
                Object val = ((Map<?,?>)subbean).get(key);
                if(val == null) {
                  val = valtype.newInstance();
                  ((Map<Object,Object>)subbean).put(key,val);
                }
                subbean = val;
              } else if(subbean.getClass().isArray()) {
                if(ndx != -1) {
                  Object val = (Array.getLength(subbean) > ndx) ? Array.get(subbean,ndx) : null;
                  if(val != null) {
                    subbean = val;
                  } else {
                    if((val = subbean.getClass().getComponentType().newInstance()) != null) {
                      Array.set(subbean,ndx,val);
                      subbean = val;
                    }
                  }
                }
              } else if(subbean instanceof List<?>) {
                if(ndx != -1) {
                  Object val = (((List<?>)subbean).size() > ndx) ? ((List<?>)subbean).get(ndx) : null;
                  if(val != null) {
                    subbean = val;
                  } else {
                    try {
                      Field field = cls.getDeclaredField(prop);
                      if(field.isAnnotationPresent(RTTI.class)) {
                        val = field.getAnnotation(RTTI.class).value().newInstance();
                      } else {
                        log.trace("No RTTI Annotation Present on {0.declaringClass.name}.{0.name}",field);
                      }
                    } catch(NoSuchFieldException x) {
                      log.trace("Can''t locate the {0} field of {1}",prop,bean,x);
                    }
                    if(val == null) {
                      if(accessor == null) {
                        accessor = getAccessor(cls,prop);
                      }
                      Type type = accessor.getGenericReturnType();
                      if(type instanceof ParameterizedType) {
                        Type[] args = ((ParameterizedType)type).getActualTypeArguments();
                        if((args != null) && (args.length > 0) && (args[0] instanceof Class<?>)) {
                          val = ((Class<Object>)args[0]).newInstance();
                        }
                      }
                    }
                    if((val == null) && !((List<?>)subbean).isEmpty()) {
                      Object testval = ((List<?>)subbean).iterator().next();
                      if(testval != null) {
                        val = testval.getClass().newInstance();
                      }
                    }
                    if(val != null) {
                      ((List<Object>)subbean).add(val);
                      subbean = val;
                    }
                  }
                }
              }
            }
            return setValue(subbean,property.substring(dot + 1),value,factory);
          }
        }
      } catch(IllegalAccessException x) {
        Method accessor;
        Class<?>[] interfaces = bean.getClass().getInterfaces();
        for(Class<?> i : interfaces) {
          try {
            if((accessor = getAccessor(i,property)) != null) {
              return accessor.invoke(bean);
            }
          } catch(IllegalAccessException y) {
              // Nop
          } catch(InvocationTargetException y) {
            IntrospectionException z = new IntrospectionException("Unable to introspect " + property + " property of " + bean);
            z.initCause(y);
            throw z;
          }
        }
        IntrospectionException y = new IntrospectionException("Unable to introspect " + property + " property of " + bean);
        y.initCause(x);
        throw y;
      } catch(InstantiationException | InvocationTargetException x) {
        IntrospectionException y = new IntrospectionException("Unable to introspect " + property + " property of " + bean);
        y.initCause(x);
        throw y;
      }
    }
    return null;
  } //setValue

  /**
   * Set the value of the (possibly dot notated) Property from the Bean
   *
   * @param bean The Bean to access 
   * @param property The Property name 
   * @param value The new Property value 
   * @return The previous property value from the bean
   * @throws IntrospectionException if we get lost in introspection
   */
  public static Object setValue (Object bean,String property,Object value) throws IntrospectionException {
    return setValue(bean,property,value,ConverterFactory.getDefaultFactory());
  } //setValue

  /**
   * Set the string value of the (possibly dot notated) Property from the 
   * supplied bean 
   *
   * @param bean The Bean to access 
   * @param property The property name 
   * @param value The property value String 
   * @return The String property value
   */
  public static String setString (Object bean,String property,String value) {
    try {
      return String.valueOf(setValue(bean,property,value));
    } catch(IntrospectionException x) {
      log.warn("Error Updating {1} property of {0} to ''{2}''",bean,property,value,x);
      return "null";
    }
  } //setString

} //*BeanUtils
